#include "pvm3.h"
#include "constantes.h"
#include "rejilla.h"
#include <time.h>
#include <stdio.h>


/**
 *Esta funcion es la que calcula cual sera el estado de una celda atendiendo a las reglas
 *del juego de Conway. Son las siguientes:
 *  -Una celda muerta nace (pasa a estar viva) si tiene 3 celdas vivas a su alrededor.
 *  -Una celda viva muere (pasa a estar muerta) si no tiene 2 o 3 celdas vivas a su alrededor.
 *@author Roman Jimenez de Andres
 *@author Luis Ciudad Garcia
 *@date 02/09/2009
 *@param estadoAnterior Se trata de una Rejilla que contiene los estados originales de las celdas.
 *@param fila Fila de la celda cuyo estado siguiente queremos calcular.
 *@param columna Columna de la celda cuyo estado siguiente queremos calcular.
 *@return El estado de la celda segun las celdas de alrededor. Al tratarse de un entero 0 se
 *considera "muerta" y 1 "viva". 
 */
int calcularConway(Rejilla estadoAnterior,int fila,int columna);

using namespace std;

int main(){
    int partes = 0;         /**< Numero de nodos que se han expandido, es decir, numero de hermanos */
    int tamSector = 3;      /**< Dimensiones de los sectores que el hijo procesara */
    int sector_id = 0;      /**< El numero de sector, comenzando desde 0. No tiene relacion con el id del proceso en PVM */
    int vecinos[9];         /**< Vector con los id de los procesos vecinos */
    int ciclos;             /**< Cantidad de ciclos a ejecutar */
    int opcion_fichero = 0; /**< Variable que indica si el usuario quiere volcar la salida a un fichero o no */
    int partesAlto = 0;     /**< Numero de sectores de la Rejilla original a lo alto */
    int partesAncho = 0;    /**< Numero de sectores de la Rejilla original a lo ancho */
    

    /**
     *Recibimos todos los parametros que nos envia el padre. Estos son:
     * -El número total de nodos
     * -Las dimensiones de los sectores
     * -El identificador de sector
     * -Un vector con el tid de los sectores vecinos que necesita el hijo
     * -La matriz que representa el sector que tiene que calcular el hijo
     * -El numero de ciclos a ejecutar
     * -La opcion de volcado
     * -Los tids de todos los hermanos
     * -El numero de sectores a lo ancho
     * -El numero de sectores a lo alto
     */
    pvm_recv(pvm_parent(), TICKET_INFO);
        pvm_upkint(&partes,1,1);
        pvm_upkint(&tamSector,1,1);
        int *nuevoEstado = new int[tamSector * tamSector];
        pvm_upkint(&sector_id,1,1);
        pvm_upkint(&vecinos[0],9,1);
        int *matriz = new int[tamSector * tamSector];
        pvm_upkint(&matriz[0],tamSector * tamSector,1);
        pvm_upkint(&ciclos,1,1);
        pvm_upkint(&opcion_fichero,1,1);
        int tids[partes];
        pvm_upkint(&tids[0],partes,1);
        pvm_upkint(&partesAncho,1,1);
        pvm_upkint(&partesAlto,1,1);
    
    /** 
     *Cuando tenemos todos los parametros creamos una Rejilla lo suficientemete grande
     *para albergar el sector que tenemos que calcular y sus ocho sectores vecinos*/
    Rejilla parte(tamSector*tamSector,tamSector*tamSector, tamSector);

    /** En el centro de la nueva Rejilla posicionamos el sector que tenemos que calcular*/
    for(int i = 0; i < tamSector; i++){
        for(int j = 0; j < tamSector; j++){
            parte.setElemento(tamSector+i,tamSector+j,matriz[(i*tamSector)+j]);
            }
        }
    
    /** Unimos el hijo al grupo de procesos de los demas hijos */
    pvm_joingroup((char *)GRUPO_HIJOS);
    
    
    /** Empezamos a iterar hasta que hayamos ejecutado todo los ciclos*/
    for(int cic = 0; cic < ciclos; cic++){

        /** Inicializamos algunas variables de control */
        int primero_finalizado = 0;
        int ultimo_finalizado = 0;
        
        /** Nos sincronizamos con el resto de hijos */
        pvm_barrier((char *)GRUPO_HIJOS,partes);

        /**
         *Ahora empezamos un proceso de comunicacion para que todos los hijos puedan obtener los
         *sectores vecinos que les hagan falta para calcular el estado siguiente de su propio
         *sector. Para ello comenzamos con un algoritmo de anillo en el que cada sector tiene
         *su turno para pedir las partes que necesite. De esta forma hasta que un proceso no indique
         *lo contrario, los hijos se dedican a comprobar si es su turno de pedir los vecinos que les hagan
         *falta o si deben estar atentos por si otro nodo requiere su sector.
         */
        while(!pvm_nrecv(-1,TICKET_ACABADO) && ultimo_finalizado == 0){
            /** 
             *Si no se ha recibido la señal de que el proceso ha terminado y el utlimo no ha acabado
             *comprobamos si nos llega la ficha o si somos el primer proceso del anillo
             */
            if((sector_id == 0 && primero_finalizado == 0) || pvm_nrecv(tids[sector_id - 1],TICKET_FICHA)){
                /** Si nos toca ejecutar vamos pidiendo uno a uno cada sector que nos haga falta*/
                for(int i = 0; i < 9; i++){
                    if(vecinos[i] != -1){

                        /** 
                         *Si necesito este sector, mando un mensaje al vecino que me indica mi vector de vecinos
                         *pidiendo dicho sector e indicando quien soy para que me devuelva el envio
                         */
                        pvm_initsend(PvmDataDefault);
                            pvm_pkint(&tids[sector_id],1,1);
                        pvm_send(vecinos[i],TICKET_SECTOR_REQUEST);
                        
                        /** Espero con bloque a que el vecino me responda y me envie su sector*/
                        pvm_recv(vecinos[i], TICKET_SECTOR_RESPONSE);
                            int *sector = new int[tamSector*tamSector];
                            pvm_upkint(&sector[0],tamSector*tamSector,1);
                        
                        /**
                         *El siguiente bloque coloca el sector que me acaba de llegar en
                         *el lugar correspondiente dependiendo de la posicion del vecino
                         *al que le haya hecho la peticion*/
                        int fila_sector = 0;
                        if(i >= 0 && i <=2) fila_sector = 0;
                        if(i >= 3 && i <=5) fila_sector = tamSector;
                        if(i >= 6 && i <=8) fila_sector = tamSector*2;
                        
                        int columna_sector = 0;
                        if(i % 3 == 0) columna_sector = 0;
                        if(i % 3 == 1) columna_sector = tamSector;
                        if(i % 3 == 2) columna_sector = tamSector*2;
                        
                        for(int f = 0; f < tamSector; f++){
                            for(int c = 0; c < tamSector; c++){
                                parte.setElemento(f+fila_sector,c+columna_sector,sector[(f*tamSector)+c]);
                                }
                            }
                        }
                    }
                /** 
                 *Una vez que tengo todos los sectores que necesito:
                 * -Si soy el ultimo nodo del anillo envio el mensaje de que ya ha terminado
                 * -Si no soy el ultimo, le envio la ficha al siguiente nodo del anillo
                 */
                if(sector_id == partes - 1){ 
                    pvm_initsend(PvmDataDefault);
                    pvm_bcast((char *)GRUPO_HIJOS,TICKET_ACABADO);
                    ultimo_finalizado = 1;
                    }
                else{             
                    pvm_initsend(PvmDataDefault);                     
                    pvm_send(tids[sector_id+1],TICKET_FICHA);
                    }

                /** Si soy el primero recuerdo que ya he terminado y aunque no tenga la ficha no me toca ejecutar */
                if(sector_id == 0)
                    primero_finalizado = 1;
                }
            else{
                /** Si no me toca ejecutar, espero con bloqueo que algun vecino quiera mi sector */
                timeval tmout;
                tmout.tv_sec = 0;
                tmout.tv_usec = 10;
                
                if(pvm_trecv(-1, TICKET_SECTOR_REQUEST,&tmout)){
                    int demandante = 0;
                    pvm_upkint(&demandante,1,1);
                    
                    /** Si recibo una peticion de sector, envio mi sector al nodo que lo ha pedido */
                    pvm_initsend(PvmDataDefault);                     
                        pvm_pkint(&matriz[0],tamSector*tamSector,1);
                    pvm_send(demandante,TICKET_SECTOR_RESPONSE);
                    }
                }
            }
            

		/** Una vez que tengo todos los vecinos de mi alrededor calculo el estado siguiente de mi sector */        
        for(int f = 0; f < tamSector; f++){
            for(int c = 0; c < tamSector; c++){
                nuevoEstado[(f*tamSector)+c] = calcularConway(parte,tamSector+f,tamSector+c);
                }
            }
		
        
        /** Actualizamos localmente las variables que se usaran en el proximo ciclo*/
        for(int f = 0; f < tamSector; f++){
            for(int c = 0; c < tamSector; c++){
                parte.setElemento(f+tamSector,c+tamSector,nuevoEstado[(f*tamSector)+c]);
                matriz[(f*tamSector)+c] = nuevoEstado[(f*tamSector)+c];
                }
            }
        
        }

    /** Y una vez calculado el nuevo estado se lo mando al padre para que lo represente graficamente */
    pvm_initsend(PvmDataDefault);
        pvm_pkint(&sector_id,1,1);
        pvm_pkint(&nuevoEstado[0],tamSector*tamSector,1);
    pvm_send(pvm_parent(),TICKET_FINAL);
    
    /** 
     *Si el usuario queria volcar la salida a un fichero comenzamos otro algoritmo de anillo
     *para que dos hijos no accedan a la vez al fichero */
    if(opcion_fichero == 1){            
        if(sector_id == 0){
            Rejilla salida(partesAlto*tamSector, partesAncho*tamSector, tamSector); 

            salida.setSector(0,0,nuevoEstado);
            salida.volcar("salida.jilla");
            }
        else{
            pvm_recv(tids[sector_id - 1],TICKET_VOLCADO);

            Rejilla salida("salida.jilla", tamSector);
            int fila = sector_id / (salida.numPartesAncho());
            int columna = sector_id % (salida.numPartesAncho());
            salida.setSector(fila * tamSector, columna * tamSector, nuevoEstado);
            salida.volcar("salida.jilla");
            }
            
        /** Si no soy el ultimo paso la ficha al siguiente */
        if(sector_id != partes - 1){
            pvm_initsend(PvmDataDefault);                     
            pvm_send(tids[sector_id+1],TICKET_VOLCADO);
            }
        }
    
    /** Una vez acabada la ejecucion comunicamos a pvm nuestra intencion de salir y abandonamos el programa */
    pvm_exit();
    
    return 1;
    }
    
int calcularConway(Rejilla estadoAnterior,int fila,int columna){
    /** 
     *Para calcular el nuevo estado de una celda, vemos el estado actual y actuamos en funcion 
     *de la cantidad de celulas vivas que tenga alrededor*/
    int estado = estadoAnterior.getElemento(fila,columna);
    int vivos = 0;
    
    for(int k = -1; k <= 1; k++){
        for(int z = -1; z <= 1; z++){
            if(estadoAnterior.getElemento(fila+k,columna+z) == 1 && !(k == 0 && z == 0)){
                vivos++;
                }
            }
        }  
    if(estado == 1){
        if(vivos == 3 || vivos == 2){
            /** Si la celda esta vivo y tiene 2 o 3 celdas vivas alredor sigue vivo */
            return 1;
            }
        }
    else{
        if(vivos == 3){
            /** Si la celda esta muerta y tiene 3 celdas vivas alrededor nace */
            return 1;
            }
        }

    /**
     * Si no se da ninguno de los casos anteriores la celda muere,
     * o bien por superpoblacion o bien por soledad
     */
    return 0;
    }

